/* Copyright (c) 2009, Enric Rodriguez (enrodri86@gmail.com)
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 	- Redistributions of source code must retain the above copyright
 * 	notice,	this list of conditions and the following disclaimer.
 * 	- Redistributions in binary form must reproduce the above copyright
 * 	notice, this list of conditions and the following disclaimer in the
 * 	documentation and/or other materials provided with the distribution.
 * 	- Neither the name of the <ORGANIZATION> nor the names of its
 * 	contributors may be used to endorse or promote products derived from
 * 	this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package es.enrodri86.mmo.auth.client;

import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import es.enrodri86.mmo.auth.shared.AuthException;
import es.enrodri86.mmo.auth.shared.Captcha;
import es.enrodri86.mmo.auth.shared.Game;
import es.enrodri86.mmo.auth.shared.GameServer;
import es.enrodri86.mmo.auth.shared.HttpHeaders;
import es.enrodri86.mmo.auth.shared.LoginException;

/**
 * You can use either HTTP or HTTPS with this class.
 * @author enrodri86
 */
public class HTTPConnection implements AuthConnection {


	private URL authServerURL;
	private HttpURLConnection conn;
	private String session;
	private boolean captchaRequired;
	private Captcha captcha;
	private Vector<Game> gameList;
	private Map<String,GameServer> serverList;
	private Game selectedGame;

	public HTTPConnection(URL url){
		authServerURL = url;
	}

	public void chooseGame(Game g) throws IOException, AuthException {
		selectedGame = g;
	}

	public void chooseGameServer(GameServer gs) throws IOException, AuthException {
		try{
			conn = (HttpURLConnection) authServerURL.openConnection();
			conn.setRequestProperty(HttpHeaders.SESSION_HEADER, session);
			conn.setRequestProperty(HttpHeaders.OP_REQUEST, HttpHeaders.OP_CHOOSE_REQUEST);
			conn.setRequestProperty(HttpHeaders.GAME_REQUEST,selectedGame.getId());
			conn.setRequestProperty(HttpHeaders.SERVER_REQUEST, gs.getId());
			conn.connect();
			String ex_code = conn.getHeaderField(HttpHeaders.EXCEPTION_CODE_HEADER);
			if(ex_code != null){
				LoginException ae = new LoginException();
				ae.setCode(Integer.parseInt(ex_code));
				ae.setDescription(conn.getHeaderField(HttpHeaders.EXCEPTION_DESC_HEADER));
				throw ae;
			}
			session = conn.getHeaderField(HttpHeaders.SESSION_HEADER);
			if(session == null){
				LoginException ae = new LoginException();
				ae.setDescription("Couldn't receive session to connect to game server.");
				throw ae;
			}

			return;

		}catch(IOException e){
			LoginException ae = new LoginException();
			ae.setStackTrace(e.getStackTrace()); //TODO better handling of unfrequent exception
			throw ae;
		}

	}

	public void connect() throws IOException, AuthException {
		try{
			conn = (HttpURLConnection) authServerURL.openConnection();
			conn.setRequestProperty(HttpHeaders.OP_REQUEST, HttpHeaders.OP_CONNECT_REQUEST);
			conn.connect();
			session = conn.getHeaderField(HttpHeaders.SESSION_HEADER);
			String ex_code = conn.getHeaderField(HttpHeaders.EXCEPTION_CODE_HEADER);
			if(ex_code != null){
				AuthException ae = new AuthException();
				ae.setCode(Integer.parseInt(ex_code));
				ae.setDescription(conn.getHeaderField(HttpHeaders.EXCEPTION_DESC_HEADER));
				throw ae;
			}else if(session == null){
				AuthException ae = new AuthException();
				ae.setDescription("Couldn't connect to the server to get a session ID.");
				throw ae;
			}
			captchaRequired = Boolean.parseBoolean(conn.getHeaderField(HttpHeaders.CAPTCHA_REQUIRED_HEADER));
			if(isCaptchaRequired()){
				try {
					captcha = Captcha.fromXML(conn.getInputStream());
				} catch (ParserConfigurationException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (SAXException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}


		}catch(IOException e){
			e.printStackTrace();
			throw e;
		}
	}

	public Captcha getCaptcha() {
		return captcha;
	}

	public List<Game> getGameList() {
		return gameList;
	}

	public List<GameServer> getGameServerList() {
		List<GameServer> r = new Vector<GameServer>();
		if(selectedGame != null){
			for(String k : selectedGame.getServerIDsList()){
				r.add(serverList.get(k));
			}
		}
		else{
			for(GameServer gs : serverList.values()){
				r.add(gs);
			}
		}
		return r;
	}

	public String getSession() {
		return session;
	}

	public boolean isCaptchaRequired() {
		return captchaRequired;
	}

	public boolean isGameSelectionRequired() {
		if(gameList != null && gameList.size() > 0) return true;
		return false;
	}

	public boolean login(String user, char[] pass, String captcha) throws LoginException {
		try{
			conn = (HttpURLConnection) authServerURL.openConnection();
			conn.setRequestProperty(HttpHeaders.SESSION_HEADER, session);
			conn.setRequestProperty(HttpHeaders.OP_REQUEST, HttpHeaders.OP_LOGIN_REQUEST);
			conn.setRequestProperty(HttpHeaders.USER_REQUEST,user);
			conn.setRequestProperty(HttpHeaders.PASS_REQUEST, new String(pass));
			if(captcha != null)	conn.setRequestProperty(HttpHeaders.CAPTCHA_REQUEST, captcha);
			conn.connect();
			String ex_code = conn.getHeaderField(HttpHeaders.EXCEPTION_CODE_HEADER);
			if(ex_code != null){
				LoginException ae = new LoginException();
				ae.setCode(Integer.parseInt(ex_code));
				ae.setDescription(conn.getHeaderField(HttpHeaders.EXCEPTION_DESC_HEADER));
				throw ae;
			}

			parseGameList(conn.getInputStream());

			return true;

		}catch(IOException e){
			LoginException ae = new LoginException();
			ae.setStackTrace(e.getStackTrace()); //TODO better handling of unfrequent exception
			throw ae;
		}
	}

	private void parseGameList(InputStream xml) {
		// TODO isGameSelectionRequired,
		gameList = new Vector<Game>();
		serverList = new Hashtable<String,GameServer>();
		try {
		SAXParser saxParser =  SAXParserFactory.newInstance().newSAXParser();
        GHandler gh = new GHandler(gameList,serverList);

			saxParser.parse(xml, gh );
		} catch (SAXException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ParserConfigurationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

}
class GHandler extends DefaultHandler{

	private Map<String, GameServer> serverList;
	private Vector<Game> gameList;
	private StringBuffer accumulator;
	private Game tempGame;
	private GameServer tempGameServer;

	public GHandler(Vector<Game> gameList, Map<String, GameServer> serverList2) {
		this.gameList = gameList; this.serverList = serverList2;
	}

	public void endElement(String namespaceURL, String localName, String qname){

		if (qname.equals("name")) {
			if(tempGameServer != null){
				tempGameServer.setName(accumulator.toString().trim());
			}else if(tempGame != null){
				tempGame.setName(accumulator.toString().trim());
			}
		}else if(qname.equals("desc")){
			if(tempGameServer != null){
				tempGameServer.setDescription(accumulator.toString().trim());
			}else if(tempGame != null){
				tempGame.setDescription(accumulator.toString().trim());
			}
		}else if(qname.equals("serverid")){
			if(tempGame != null){
				tempGame.addServerID(accumulator.toString().trim());
			}
		}else if(qname.equals("host")){
			if(tempGameServer != null){
				tempGameServer.setHost(accumulator.toString().trim());
			}
		}else if(qname.equals("port")){
			if(tempGameServer != null){
				tempGameServer.setPort(Integer.parseInt(accumulator.toString().trim()));
			}
		}else if(qname.equals("status")){
			if(tempGameServer != null){
				tempGameServer.setStatus(Integer.parseInt(accumulator.toString().trim()));
			}
		}else if(qname.equals("url")){
			if(tempGameServer != null){
				tempGameServer.setUrl(accumulator.toString().trim());
			}
		}else if(qname.equals("game")){
			gameList.add(tempGame); tempGame = null;
		}else if(qname.equals("server")){
				serverList.put(tempGameServer.getId(),tempGameServer); tempGameServer = null;

		}
	}

	public void startDocument() {
		accumulator = new StringBuffer();
	}

	public void characters(char[] buffer, int start, int length) {
		accumulator.append(buffer, start, length);
	}

	public void startElement(String namespaceURL, String localName, String qname, Attributes attributes) {
		if(qname.equals("games")){
			serverList = null;
		}
		if(qname.equals("game")){
			tempGame = new Game();
			tempGame.setId(attributes.getValue("id"));
		}
		if(qname.equals("server")){
			tempGameServer = new GameServer();
			tempGameServer.setId(attributes.getValue("id"));
		}

		accumulator.setLength(0);
	}
}